--[[

	-----------------------------
	nnCast (Announcer for caster)
	-----------------------------
	캐스터용 알림이

--]]

nnCast = LibStub("AceAddon-3.0"):NewAddon("nnCast", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0")

-------------------------------------------------------------------------------------
-- 변수
-------------------------------------------------------------------------------------
	local listSkill = {
		success = {},
		missed = {},
		aura = {},
		debuff = {},
		resurrect = {},
	}
	local listMessage = {}
	local savedMessage = {}

	local trackedEvents = {}

-------------------------------------------------------------------------------------
-- 상수
-------------------------------------------------------------------------------------
	local isMine = {
		player = true,
		pet = true,
		vehicle = true,
	}

	local playerName = UnitName("player")

	-----------------
	-- 주문 실패 종류
	-----------------
	local listMiss = {
		DODGE = "피함",
		ABSORB = "흡수",
		RESIST = "저항함",
		PARRY = "막음",
		MISS = "빗나감",
		BLOCK = "방어함",
		REFLECT = "반사함",
		DEFLECT = "튕김",
		IMMUNE = "면역",
		EVADE = "벗어남",
		TOTEM = "마법 흡수 토템",
	}

	---------------
	-- 외부 UI 관련
	---------------
	local color = {
		default	= { r = 1.0, g = 1.0, b = 1.0 },
		good	= { r = 1.0, g = 1.0, b = 0.0 },
		bad		= { r = 1.0, g = 0.0, b = 0.0 },
	}

	local ui = {
		dispel = {
			color = color.default,
			position = "UI_INCOMING",
			critical = false,
		},
		ccbreak = {
			color = color.bad,
			position = "UI_OUTGOING",
			critical = false,
		},
		interrupt = {
			color = color.good,
			position = "UI_NOTIFICATION",
			critical = true,
		},
		hostileinterrupt = {
			color = color.bad,
			position = "UI_NOTIFICATION",
			critical = true,
		},
		reflect = {
			color = color.good,
			position = "UI_NOTIFICATION",
			critical = true,
		},
		success = {
			color = color.default,
			position = "UI_OUTGOING",
			critical = false,
		},
		missed = {
			color = color.bad,
			position = "UI_NOTIFICATION",
			critical = true,
		},
		aura = {
			color = color.good,
			position = "UI_OUTGOING",
			critical = false,
		},
		debuff = {
			color = color.bad,
			position = "UI_NOTIFICATION",
			critical = true,
		},
		resurrect = {
			color = color.good,
			position = "UI_NOTIFICATION",
			critical = false,
		},
	}

-------------------------------------------------------------------------------------
-- 함수
-------------------------------------------------------------------------------------
	-----------
	-- 디버깅용
	-----------
	local function dump(name, value, depth)
		local space = string.rep("    ", depth)
		local returnValue = name .. " = "

		if type(value) == "table" then
			returnValue = returnValue .. "{\n"

			for i, v in pairs(value) do
				returnValue = returnValue .. dump(i, v, depth + 1)
			end

			returnValue = returnValue .. "}"
		elseif type(value) == "boolean" then
			if value then
				returnValue = returnValue .. "true"
			else
				returnValue = returnValue .. "false"
			end
		elseif type(value) == "number" then
			returnValue = returnValue .. value
		elseif type(value) == "string" then
			returnValue = returnValue .. "\"" .. value .. "\""
		else
			returnValue = returnValue .. type(value)
		end
		return returnValue .. ",\n"
	end

	--------------
	-- 서버명 제거
	--------------
	local function GetName(name)
		if name ~= nil then
			local loc = string.find(name, "-")
			local shortname
			if loc then
				shortname = string.sub(name, 0, loc - 1)
			end
			return shortname or name
		else
			return UNKNOWN
		end
	end

	--------------
	-- 스킬명 치환
	--------------
	local function GetSkillName(skillId, skillName)
		if skillName == nil then
			skillName = "평타"
		elseif nnCast.db.profile.useDestLink then
			skillName = GetSpellLink(skillId)
		end
		return skillName
	end

	--------------
	-- 메시지 치환
	--------------
	local function ReplaceMessage(msgFormat, varList)
		for i, v in pairs(varList) do
			msgFormat = string.gsub(msgFormat, "%[" .. i .. "%]", v)
		end
		return msgFormat
	end

	-------------------
	-- 스킬 목록에 추가
	-------------------
	function nnCast:SetSkillList(key, list)
		for i, v in pairs(list) do
			if not GetSpellInfo(i) then print("spell id ".. i .." removed!") end
			listSkill[key][i] = v
		end
	end

	-------------------
	-- 스킬 목록을 반환
	-------------------
	function nnCast:GetSkillList(key)
		local list = {}
		for i in pairs(listSkill[key]) do
			table.insert(list, i)
		end
		return list
	end

	---------------------
	-- 귓속말 대상 초기화
	---------------------
	function nnCast:ClearWhisper()
		for i in pairs(self.db.profile.whisper) do
			self.db.profile.whisper[i] = ""
		end
	end

	--------------------------
	-- 귓속말 대상 유효성 검사
	--------------------------
	function nnCast:CheckWhisper()
		for i in pairs(self.db.profile.whisper) do
			if not string.find(self.db.profile.whisper[i], "^[a-zA-Z가-힣]*$") then
				self.db.profile.whisper[i] = ""
			end
		end

		if not string.find(self.db.profile.userChannel, "^[a-zA-Z가-힣0-9_]*$") then
			self.db.profile.userChannel = ""
		end
	end

	----------------------
	-- 외부 UI 메세지 출력
	----------------------
	function nnCast:PrintUI(msgType, msg)
		local set = ui[msgType]

		local r = set.color.r
		local g = set.color.g
		local b = set.color.b
		local position
		local critical = set.critical

		if self.db.profile.position[msgType] ~= nil then
			set.position = self.db.profile.position[msgType]
		end

		----------------------
		-- ScrollingCombatText
		----------------------
		if _G.SCT and _G.SCT:IsEnabled() then
			if set.position == "UI_NOTIFICATION" then
				position = SCT.FRAME1
			elseif set.position == "UI_INCOMING" then
				position = SCT.MSG
			elseif set.position == "UI_OUTGOING" then
				position = SCT.FRAME2
			end
			SCT:DisplayCustomEvent(msg, {r = r, g = g, b = b}, critical, position)
		end
		------------------------------
		-- Mik's Scrolling Battle Text
		------------------------------
		-- if _G.MikSBT and not _G.MikSBT.IsModDisabled() then
		if _G.MikSBT then
			if set.position == "UI_NOTIFICATION" then
				position = MikSBT.DISPLAYTYPE_NOTIFICATION
			elseif set.position == "UI_INCOMING" or set.position == "UI_OUTGOING" then
				position = MikSBT.DISPLAYTYPE_OUTGOING
			end
			MikSBT.DisplayMessage(msg, position, critical, r * 255, g * 255, b * 255)
		end
		---------
		-- Parrot
		---------
		if _G.Parrot and _G.Parrot:IsActive() then
			Parrot:ShowMessage(msg, "Notification", critical, r, g, b)
		end
		----------------------
		-- BlinkCombatFeedBack
		----------------------
		-- if _G.BlinkCombatFeedbackFrame then
		-- BCF_DisplayMessage({text = msg, rgb = {r = r, g = g, b = b}, paneltype = critical, font = "Fonts\\2002b.TTF"})
		if bcfDB and bcfDB["enable"] then
			if critical then
				critical = "crit"
			else
				critical = "sticky"
			end
			_G["BlinkCombatFeedback"].bScroll:Display(
				'message',		("|cFF%02X%02X%02X%s|r"):format(r*255, g*255, b*255, msg),
				'type',			critical,
				'textsize',		24,
				'font',			bcfDB["outgoingHit.font"],
				'outline',		'',
				'area',			_G["BlinkCombatFeedback"].bScroll:GetScrollArea("Crit"),
				'align',		"center"
			)
		end
		----------------------
		-- Blizzard_CombatText
		----------------------
		if _G.SHOW_COMBAT_TEXT and tostring(_G.SHOW_COMBAT_TEXT) ~= "0" then
			if critical then
				critical = "crit"
			else
				critical = ""
			end
			CombatText_AddMessage(msg, CombatText_StandardScroll, r, g, b, critical)
		end
	end

	--------------
	-- 스팸 필터링
	--------------
	local displayMessage = {}

	function nnCast:Message(spellId, delay, msgType, msgFormat, varList)
		if not spellId then spellId = 6603 end
		if not savedMessage[spellId] then savedMessage[spellId] = {} end
		local target = varList["대상"] or ""
		savedMessage[spellId]["count"] = (savedMessage[spellId]["count"] or 0) + 1
		if savedMessage[spellId]["count"] == 1 then
			savedMessage[spellId]["firsttarget"] = target
			savedMessage[spellId]["multitarget"] = false
		elseif savedMessage[spellId]["count"] > 1 and not savedMessage[spellId]["multitarget"] and savedMessage[spellId]["firsttarget"] ~= target then
			savedMessage[spellId]["multitarget"] = true
		end
		savedMessage[spellId]["msgType"] = msgType
		savedMessage[spellId]["msgFormat"] = msgFormat
		savedMessage[spellId]["varList"] = varList
		if displayMessage[spellId] then
			self:CancelTimer(displayMessage[spellId])
		end
		displayMessage[spellId] = nil
		displayMessage[spellId] = self:ScheduleTimer("DisplayMessage", delay or 0.2, spellId)
	end


	--------------
	-- 메세지 출력
	--------------
	function nnCast:DisplayMessage(spellId)
		if not savedMessage[spellId] then return end 
		local count = savedMessage[spellId]["count"]
		local msgType = savedMessage[spellId]["msgType"]
		local msgFormat = savedMessage[spellId]["msgFormat"]
		local varList = savedMessage[spellId]["varList"] 
		local isMultiTarget = savedMessage[spellId]["multitarget"]
		if count > 1 then
			varList["주문"] = varList["주문"].."x"..count
			if isMultiTarget then
				varList["대상"] = "다수"
			end
		end

		msg = ReplaceMessage(msgFormat, varList)

		---------------
		-- 외부 UI 출력
		---------------
		if self.db.profile.useAddon == true then
			self:PrintUI(msgType, msg)
		end

		------------
		-- 채널 출력
		------------
		local channel = self.db.profile.channel[msgType]
		local _, instanceType = IsInInstance()
		
		if not channel then channel = "PARTY" end

		if (channel == "PARTY" or channel == "RAID" or channel == "RAID_WARNING") and instanceType == "pvp" then
			if self.db.profile.alwaysAlarm then
				channel = "CHAT_FRAME"
			else
				channel = "NONE"
			end
		end

		if (channel == "RAID" or channel == "RAID_WARNING") and IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and instanceType == "raid" then
			channel = "INSTANCE_CHAT"
		end

		if (channel == "PARTY") and IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and instanceType == "raid" then
			if self.db.profile.alwaysAlarm then
				channel = "CHAT_FRAME"
			else
				channel = "NONE"
			end
		end

		if (channel == "PARTY" or channel == "RAID" or channel == "RAID_WARNING") and IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
			channel = "INSTANCE_CHAT"
		end

		if channel == "RAID_WARNING" and (not IsInRaid() or not (UnitIsGroupLeader("player") or UnitIsGroupAssistant("player"))) then
			channel = "RAID"
		end

		if channel == "RAID" and not IsInRaid() then
			channel = "PARTY"
		end

		if channel == "PARTY" and not IsInGroup(LE_PARTY_CATEGORY_HOME) then
			if self.db.profile.alwaysAlarm then
				channel = "CHAT_FRAME"
			else
				channel = "NONE"
			end
		end

		if channel == "RAID_WARNING" then
			msg = "[" .. playerName .. "] " .. msg
		end
		
		if channel == "CHAT_FRAME" then
			self:Print(msg)
		elseif channel == "USER_CHANNEL" then
			local userChannel = GetChannelName(self.db.profile.userChannel)
			if userChannel > 0 then
				SendChatMessage(msg, "CHANNEL", nil, userChannel)
			end
		elseif channel ~= "NONE" then
			SendChatMessage(msg, channel)
		end

		--------------
		-- 귓속말 출력
		--------------
		if self.db.profile.whisper[msgType] ~= "" then
			SendChatMessage(msg, "WHISPER", nil, self.db.profile.whisper[msgType])
		end

		-------------------
		-- 자체 에러 프레임
		-------------------
		if self.db.profile.useErrorFrame then
			UIErrorsFrame:AddMessage(msg, r, g, b, 1.0, UIERRORS_HOLD_TIME)
		end
		
		savedMessage[spellId] = nil
	end

	---------
	-- 초기화
	---------
	function nnCast:OnInitialize()
		self:SetupOptions()

		if self.db.profile.onLoadClear then
			self:ClearWhisper()
		end
		self:CheckWhisper()

		listMessage = self:GetMessage()
	end

	function nnCast:OnEnable()
		self:LoadEvents()
	end

	function nnCast:OnDisable()
		self:UnregisterAllEvents()
	end

	--------------
	-- 이벤트 로드
	--------------
	function nnCast:LoadEvents()
		trackedEvents = {}

		-------------------
		-- 버프/디버프 해제
		-------------------
		if self.db.profile.dispel then
			local class = select(2, UnitClass("player"))
			if class == "MAGE" or class == "WARLOCK" then
				trackedEvents["SPELL_STOLEN"] = true
			end
			trackedEvents["SPELL_DISPEL"] = true
		end
		-----------------
		-- 군중 제어 해제
		-----------------
		if self.db.profile.ccbreak then
			trackedEvents["SPELL_AURA_BROKEN"] = true
			trackedEvents["SPELL_AURA_BROKEN_SPELL"] = true
		end
		------------
		-- 주문 차단
		------------
		if self.db.profile.interrupt then
			trackedEvents["SPELL_INTERRUPT"] = true
		end
		------------
		-- 주문 성공
		------------
		if self.db.profile.success or self.db.profile.missed then
			trackedEvents["SPELL_CAST_SUCCESS"] = true
		end
		------------
		-- 전투 부활
		------------
		if self.db.profile.resurrect then
			trackedEvents["SPELL_RESURRECT"] = true
		end
		------------
		-- 주문 실패
		------------
		if self.db.profile.missed or self.db.profile.reflect then
			trackedEvents["SPELL_MISSED"] = true
		end
		-------
		-- 버프
		-------
		if self.db.profile.aura then
			trackedEvents["SPELL_AURA_APPLIED"] = true
			trackedEvents["SPELL_AURA_REFRESH"] = true
		end

		self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	end

	function nnCast:COMBAT_LOG_EVENT_UNFILTERED(arg1, timestamp, event, _, srcGUID, srcName, srcFlags, _, destGUID, destName, destFlags, _, ...)
 		if not trackedEvents[event] then
			return
		end

		local args = { ... }

		---------------------------------
		-- 시전자 : 자신 or 자신의 소환수
		---------------------------------
		if bit.band(srcFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE then
			-------------------
			-- 버프/디버프 해제
			-------------------
			if event == "SPELL_DISPEL" or event == "SPELL_STOLEN" then
				self:DispelHandler(destName, destFlags, args[1], args[2], args[4], args[5])
			-----------------
			-- 군중 제어 해제
			-----------------
			elseif event == "SPELL_AURA_BROKEN" or event == "SPELL_AURA_BROKEN_SPELL" then
				self:CcBreakHandler(srcName, destName, args[1], args[2], args[4], args[5])
			------------
			-- 주문 차단
			------------
			elseif event == "SPELL_INTERRUPT" then
				self:InterruptHandler(destName, args[1], args[2], args[4], args[5])
			------------
			-- 주문 성공
			------------
			elseif event == "SPELL_CAST_SUCCESS" then
				if destName == "마법흡수 토템" then
					if self.db.profile.missed then
						self:MissedHandler(destName, args[1], args[2], "TOTEM")
					end
				else
					if self.db.profile.success then
						self:SuccessHandler(destName, args[1], args[2])
					end
				end
			-----------
			-- 부활
			-----------
			elseif self.db.profile.resurrect and event == "SPELL_RESURRECT"  then
				self:ResurrectHandler(destName, args[1], args[2])
			-----------
			-- 주문 실패
			-----------
			elseif self.db.profile.missed and event == "SPELL_MISSED" then
				self:MissedHandler(destName, args[1], args[2], args[4], args[5])
			-------
			-- 버프
			-------
			elseif event == "SPELL_AURA_APPLIED" or event == "SPELL_AURA_REFRESH" then
				self:AuraHandler(destGUID, destName, args[1], args[2])
			end
		------------------------------------
		-- 시전 대상 : 자신 or 자신의 소환수
		------------------------------------
		elseif bit.band(destFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE then
			--------------
			-- 주문 피차단
			--------------
			if event == "SPELL_INTERRUPT" then
				self:HostileInterruptHandler(srcName, destName, args[1], args[2], args[4], args[5])
			------------
			-- 주문 반사
			------------
			elseif self.db.profile.reflect and event == "SPELL_MISSED" and args[4] == "REFLECT" then
				self:ReflectHandler(srcName, args[1], args[2])
			---------
			-- 디버프
			---------
			elseif self.db.profile.debuff and event == "SPELL_AURA_APPLIED" then
				self:DebuffHandler(srcName, destGUID, destName, args[1], args[2])
			end
		end
	end

	-------------------
	-- 버프/디버프 해제
	-------------------
	function nnCast:DispelHandler(destName, destFlags, srcSkillId, srcSkillName, destSkillId, destSkillName)
		if self.db.profile.dispelEnemy and (bit.band(destFlags, COMBATLOG_OBJECT_REACTION_FRIENDLY) == COMBATLOG_OBJECT_REACTION_FRIENDLY) then
			return
		end
		self:Message(srcSkillId, 0.2, "dispel", listMessage.dispel, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName), ["대상주문"] = GetSkillName(destSkillId, destSkillName)})
	end

	-----------------
	-- 군중 제어 해제
	-----------------
	function nnCast:CcBreakHandler(srcName, destName, srcSkillId, srcSkillName, destSkillId, destSkillName)
		self:Message(srcSkillId, 0.05, "ccbreak", listMessage.ccbreak, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName), ["해제자"] = GetName(srcName), ["해제주문"] = GetSkillName(destSkillId, destSkillName)})
	end

	------------
	-- 주문 차단
	------------
	function nnCast:InterruptHandler(destName, srcSkillId, srcSkillName, destSkillId, destSkillName)
		self:Message(srcSkillId, 0.05, "interrupt", listMessage.interrupt, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName), ["대상주문"] = GetSkillName(destSkillId, destSkillName)})
	end

	--------------
	-- 주문 피차단
	--------------
	function nnCast:HostileInterruptHandler(srcName, destName, srcSkillId, srcSkillName, destSkillId, destSkillName)
		self:Message(srcSkillId, 0.05, "hostileinterrupt", listMessage.hostileinterrupt, {["주문"] = GetSkillName(destSkillId, destSkillName), ["대상"] = GetName(srcName), ["대상주문"] = GetSkillName(srcSkillId, srcSkillName)})
	end

	------------
	-- 주문 반사
	------------
	function nnCast:ReflectHandler(srcName, srcSkillId, srcSkillName)
		self:Message(srcSkillId, 0.05, "reflect", listMessage.reflect, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(srcName)})
	end

	------------
	-- 주문 성공
	------------
	function nnCast:SuccessHandler(destName, srcSkillId, srcSkillName)
		if listSkill.success[srcSkillName] or listSkill.success[srcSkillId] then
			self:Message(srcSkillId, 0.2, "success", listMessage.success, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName or playerName)})
		end
	end

	------------
	-- 주문 실패
	------------
	function nnCast:MissedHandler(destName, srcSkillId, srcSkillName, missType)
		if listSkill.missed[srcSkillName] or listSkill.missed[srcSkillId] then
			self:Message(srcSkillId, 0.2, "missed", listMessage.missed, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName), ["원인"] = (listMiss[missType] or "알 수 없음")})
		end
	end

	------------
	-- 전투 부활
	------------
	function nnCast:ResurrectHandler(destName, srcSkillId, srcSkillName)
		if listSkill.resurrect[srcSkillName] or listSkill.resurrect[srcSkillId] then
			self:Message(srcSkillId, 0.2, "resurrect", listMessage.resurrect, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName or playerName)})
		end
	end

	-------
	-- 버프
	-------
	function nnCast:AuraHandler(destGUID, destName, srcSkillId, srcSkillName)
		if listSkill.aura[srcSkillName] or listSkill.aura[srcSkillId] then
			local name, duration, caster, _

			if self.db.profile.duration then
				if destGUID == UnitGUID("playertarget") then
					local filter
					if UnitIsFriend("player", "playertarget") then
						filter = "HELPFUL"
					else
						filter = "HARMFUL"
					end
					for i = 1, 40 do
						name, _, _, _, _, duration, _, caster = UnitAura("playertarget", i, filter)
						if isMine[caster] and name == srcSkillName then
							break
						end
					end
				elseif destGUID == UnitGUID("player") then
					for i = 1, 40 do
						name, _, _, _, _, duration, _, caster = UnitBuff("player", i)
						if isMine[caster] and name == srcSkillName then
							break
						end
					end
				elseif destGUID == UnitGUID("pet") then
					for i = 1, 40 do
						name, _, _, _, _, duration, _, caster = UnitBuff("pet", i)
						if isMine[caster] and name == srcSkillName then
							break
						end
					end
				end
			end

			if duration ~= nil and duration > 0 then
				self:Message(srcSkillId, 0.2, "aura", listMessage.aura_duration, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName), ["시간"] = duration})
			else
				self:Message(srcSkillId, 0.2, "aura", listMessage.aura, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(destName)})
			end
		end
	end

	---------
	-- 디버프
	---------
	function nnCast:DebuffHandler(srcName, destGUID, destName, srcSkillId, srcSkillName)
		if listSkill.debuff[srcSkillName] or listSkill.debuff[srcSkillId] then
			local target, duration, _
			if self.db.profile.duration then
				if destGUID == UnitGUID("player") then
					target = "player"
				elseif destGUID == UnitGUID("pet") then
					target = "pet"
				end
				if target ~= nil then
					_, _, _, _, _, duration = UnitDebuff(target, srcSkillName)

					if duration ~= nil then
						_, _, _, _, _, duration = UnitBuff(target, srcSkillName)
					end
				end
			end

			if duration ~= nil and duration > 0 then
				self:Message(srcSkillId, 0.2, "debuff", listMessage.debuff_duration, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(srcName), ["시간"] = duration})
			else
				self:Message(srcSkillId, 0.2, "debuff", listMessage.debuff, {["주문"] = GetSkillName(srcSkillId, srcSkillName), ["대상"] = GetName(srcName)})
			end
		end
	end
